﻿using Avin.Infrastructure.Entity;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Linq.Expressions;

namespace Avin.Infrastructure.Linq
{
    public static class ExpressionResolver
    {
        /// <summary>
        /// 运算符号
        /// </summary>
        public readonly static string[] OperatorSymbol = new string[] { "+", "-", "*", "/" };

        /// <summary>
        /// 条件符号
        /// </summary>
        public readonly static string[] ConditionSymbol = new string[] { "=", ">", "<", ">=", "<=", "<>" };

        /// <summary>
        /// 解析运算符
        /// </summary>
        /// <param name="expType"></param>
        /// <returns></returns>
        public static string ExpressionTypeCast(ExpressionType expType)
        {
            switch (expType)
            {
                case ExpressionType.And:
                    return "&";
                case ExpressionType.AndAlso:
                    return "and";
                case ExpressionType.Equal:
                    return "=";
                case ExpressionType.GreaterThan:
                    return ">";
                case ExpressionType.GreaterThanOrEqual:
                    return ">=";
                case ExpressionType.LessThan:
                    return "<";
                case ExpressionType.LessThanOrEqual:
                    return "<=";
                case ExpressionType.NotEqual:
                    return "<>";
                case ExpressionType.Or:
                    return "|";
                case ExpressionType.OrElse:
                    return "or";
                case ExpressionType.Add:
                case ExpressionType.AddChecked:
                    return "+";
                case ExpressionType.Subtract:
                case ExpressionType.SubtractChecked:
                    return "-";
                case ExpressionType.Divide:
                    return "/";
                case ExpressionType.Multiply:
                case ExpressionType.MultiplyChecked:
                    return "*";
                default:
                    throw new InvalidCastException("不支持的运算符");
            }
        }

        /// <summary>
        /// 解析条件表达式
        /// </summary>
        /// <param name="expression"></param>
        public static string ResolveConditionExpression(Expression expression, DbProviderFactory dbProviderFactory, QueryInfo queryInfo)
        {
            string strResult = string.Empty;
            if (expression is BinaryExpression bExp)// 二元表达式
            {
                var paramCount1 = queryInfo.ParamInfos.Count;
                strResult += '(';
                strResult += ResolveConditionExpression(bExp.Left, dbProviderFactory, queryInfo);
                var symbol = ExpressionTypeCast(bExp.NodeType);
                var strTemp = ResolveConditionExpression(bExp.Right, dbProviderFactory, queryInfo);
                var paramCount2 = queryInfo.ParamInfos.Count;

                if ((symbol == "=" || symbol == "<>") &&
                    paramCount2 == paramCount1 + 1 &&
                    queryInfo.ParamInfos[^1].Value == DBNull.Value)//Null处理
                {
                    if (symbol == "=")
                        strResult += " is null ";
                    else
                        strResult += " is not null ";
                }
                else
                {
                    strResult += " " + symbol + " ";
                    strResult += strTemp;
                }
                strResult += ')';
            }
            else if (expression is MemberExpression mExp)//成员表达式
            {
                var value = GetValueFromClosureType(mExp);
                if (value != null)//如果是常量表达式
                {
                    strResult += "@VAR" + queryInfo.ParamInfos.Count;
                    queryInfo.ParamInfos.Add(
                        new ParamInfo
                        {
                            Name = "@VAR" + queryInfo.ParamInfos.Count,
                            Value = value,
                            HashCode = value.GetHashCode()
                        });
                }
                else
                {
                    var tableInfo = queryInfo.TableInfos.SingleOrDefault(a => a.HashCode == mExp.Expression.Type.GetHashCode());
                    if (tableInfo == null)
                    {
                        var strFieldName = GetFieldNameFromQueryInfo(mExp, dbProviderFactory, queryInfo);
                        if (string.IsNullOrWhiteSpace(strFieldName))
                        {
                            strResult += EntityInitializer.GetColumnName(dbProviderFactory, mExp.Type, mExp.Member.Name);
                        }
                        else
                        {
                            strResult += strFieldName;
                        }
                    }
                    else
                    {
                        if (mExp.Type == typeof(bool))
                            strResult += '(';
                        strResult += tableInfo.OtherName + "." + EntityInitializer.GetColumnName(dbProviderFactory, mExp.Type, mExp.Member.Name);
                        if (mExp.Type == typeof(bool))
                        {
                            if (queryInfo.WhereIsInNot)
                                strResult += " = 0";
                            else
                                strResult += " = 1";
                            strResult += ')';
                            queryInfo.WhereIsInNot = false;
                        }
                    }
                }
            }
            else if (expression is MethodCallExpression mcExp)//方法调用表达式
            {
                var mc_mExp = mcExp.Object as MemberExpression;
                if (mc_mExp != null && mc_mExp.Expression.NodeType == ExpressionType.Parameter)//如果是参数
                {
                    var strFieldName = GetFieldNameFromQueryInfo(mc_mExp, dbProviderFactory, queryInfo);
                    if (mcExp.Method.Name == "Contains")
                    {
                        strResult += "(" + strFieldName + " like " + ResolveConditionExpression(mcExp.Arguments[0], dbProviderFactory, queryInfo) + ")";
                        queryInfo.ParamInfos[^1].Value = "%" + queryInfo.ParamInfos[^1].Value.ToString() + "%";
                    }
                    else if (mcExp.Method.Name == "IndexOf")
                    {
                        strResult += "charindex(" + ResolveConditionExpression(mcExp.Arguments[0], dbProviderFactory, queryInfo) + "," + strFieldName + ")";
                    }
                }
                else
                {
                    var value = Expression.Lambda(mcExp).Compile().DynamicInvoke();
                    var paramInfo = queryInfo.ParamInfos.SingleOrDefault(a => a.HashCode == value.GetHashCode());
                    if (paramInfo == null)
                    {
                        strResult += "@VAR" + queryInfo.ParamInfos.Count;
                        queryInfo.ParamInfos.Add(new ParamInfo
                        {
                            Name = "@VAR" + queryInfo.ParamInfos.Count,
                            Value = value,
                            HashCode = value.GetHashCode()
                        });
                    }
                    else
                    {
                        strResult += paramInfo.Name;
                    }
                }
            }
            else if (expression is UnaryExpression uExp)// 一元表达式：!p.IsDeleted
            {
                if (uExp.NodeType == ExpressionType.Not)
                    queryInfo.WhereIsInNot = true;

                strResult += ResolveConditionExpression(uExp.Operand, dbProviderFactory, queryInfo);
            }
            else if (expression is LambdaExpression lExp)//表达式
            {
                strResult += ResolveConditionExpression(lExp.Body, dbProviderFactory, queryInfo);
            }
            else if (expression is ConstantExpression cExp)//常量表达式
            {
                var paramInfo = queryInfo.ParamInfos.SingleOrDefault(a => a.HashCode == cExp.Value.GetHashCode());
                if (paramInfo == null)
                {
                    strResult += "@VAR" + queryInfo.ParamInfos.Count;
                    queryInfo.ParamInfos.Add(
                        new ParamInfo
                        {
                            Name = "@VAR" + queryInfo.ParamInfos.Count,
                            Value = cExp.Value ?? DBNull.Value,
                            HashCode = (cExp.Value ?? DBNull.Value).GetHashCode()
                        });
                }
                else
                {
                    strResult += paramInfo.Name;
                }
            }
            else
            {
                Console.WriteLine(expression.GetType());
            }
            return strResult;
        }

        /// <summary>
        /// 解析运算表达式
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public static string ResolveOperatorExpression(Expression expression, DbProviderFactory dbProviderFactory, QueryInfo queryInfo)
        {
            string strResult = string.Empty;
            if (expression is BinaryExpression bExp)//二元表达式
            {
                var symbol = ExpressionTypeCast(expression.NodeType);
                if (OperatorSymbol.Contains(symbol))
                {
                    strResult += "(" + ResolveOperatorExpression(bExp.Left, dbProviderFactory, queryInfo);
                    strResult += " " + symbol + " ";
                    strResult += ResolveOperatorExpression(bExp.Right, dbProviderFactory, queryInfo) + ")";
                }
            }
            else if (expression is MemberExpression mExp)//成员表达式
            {
                if (mExp.Expression.NodeType == ExpressionType.Constant)//如果是常量表达式
                {
                    var value = GetValueFromClosureType(mExp);
                    strResult += "@VAR" + queryInfo.ParamInfos.Count;
                    queryInfo.ParamInfos.Add(
                        new ParamInfo
                        {
                            Name = "@VAR" + queryInfo.ParamInfos.Count,
                            Value = value ?? DBNull.Value,
                            HashCode = value.GetHashCode()
                        });
                }
                else
                {
                    var strFieldName = GetFieldNameFromQueryInfo(mExp, dbProviderFactory, queryInfo);
                    if (!string.IsNullOrWhiteSpace(strFieldName))
                    {
                        strResult += strFieldName;
                    }
                    else
                    {
                        var value = GetValueFromClosureType(mExp);
                        strResult += "@VAR" + queryInfo.ParamInfos.Count;
                        queryInfo.ParamInfos.Add(
                        new ParamInfo
                        {
                            Name = "@VAR" + queryInfo.ParamInfos.Count,
                            Value = value ?? DBNull.Value,
                            HashCode = (value ?? DBNull.Value).GetHashCode()
                        });
                    }
                }
            }
            else if (expression is MethodCallExpression mcExp)//方法调用表达式
            {
                string strMethodName = mcExp.Method.Name;
                if (strMethodName == "Count")
                {
                    strResult += "count(1)";
                }
                else if (strMethodName == "Sum")
                {
                    strResult += "sum(" + ResolveOperatorExpression(mcExp.Arguments[1], dbProviderFactory, queryInfo) + ")";
                }
            }
            else if (expression is UnaryExpression uExp)//一元表达式
            {
                strResult += ResolveOperatorExpression(uExp.Operand, dbProviderFactory, queryInfo);
            }
            else if (expression is LambdaExpression lExp)//表达式
            {
                strResult += ResolveOperatorExpression(lExp.Body, dbProviderFactory, queryInfo);
            }
            else if (expression is ConstantExpression cExp)//常量表达式
            {
                var paramInfo = queryInfo.ParamInfos.SingleOrDefault(a => a.HashCode == cExp.Value.GetHashCode());
                if (paramInfo == null)
                {
                    strResult += "@VAR" + queryInfo.ParamInfos.Count;
                    queryInfo.ParamInfos.Add(new ParamInfo
                    {
                        Name = "@VAR" + queryInfo.ParamInfos.Count,
                        Value = cExp.Value,
                        HashCode = cExp.Value.GetHashCode()
                    });
                }
                else
                {
                    strResult += paramInfo.Name;
                }
            }
            return strResult;
        }

        /// <summary>
        /// 判断是否继承IQueryable
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsAssignableFromIQueryable(Type type)
        {
            var interfaces = type.GetInterfaces();
            if (interfaces.Length > 0)
            {
                foreach (var i in interfaces)
                {
                    if (!i.IsGenericType)
                        continue;

                    var t = i.GetGenericTypeDefinition();
                    if (t == typeof(IQueryable<>))
                        return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 获取属性名称
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="dbProviderFactory"></param>
        /// <param name="queryInfo"></param>
        /// <returns></returns>
        private static string GetFieldNameFromQueryInfo(MemberExpression expression, DbProviderFactory dbProviderFactory, QueryInfo queryInfo)
        {
            var tableInfo = queryInfo.TableInfos.SingleOrDefault(a => a.HashCode == expression.Expression.Type.GetHashCode());
            if (tableInfo != null)//原来类型
                return tableInfo.OtherName + "." + EntityInitializer.GetColumnName(dbProviderFactory, expression.Type, expression.Member.Name);

            //select后的类型
            var selectInfo = queryInfo.SelectInfos.SingleOrDefault(a => a.OtherName == expression.Member.Name);
            if (selectInfo != null)
                return selectInfo.FieldName;
            var groupByInfo = queryInfo.GroupNewInfos.SingleOrDefault(a => a.OtherName == expression.Member.Name);
            if (groupByInfo != null)
                return groupByInfo.FieldName;
            return string.Empty;
        }

        /// <summary>
        /// 获取值
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public static object GetValueFromClosureType(MemberExpression expression)
        {
            List<string> fieldNames = new();
            while (expression.Expression.NodeType == ExpressionType.MemberAccess)
            {
                fieldNames.Add(expression.Member.Name);
                expression = expression.Expression as MemberExpression;
            }
            object data = null;
            if (expression.Expression.NodeType == ExpressionType.Constant)
            {
                fieldNames.Add(expression.Member.Name);
                if (fieldNames.Count > 0)
                    fieldNames.Reverse();
                data = (expression.Expression as ConstantExpression).Value;
                foreach (var s in fieldNames)
                {
                    var field = data.GetType().GetField(s);
                    if (field != null)
                    {
                        data = field.GetValue(data);
                    }
                    else
                    {
                        var property = data.GetType().GetProperty(s);
                        if (property != null)
                        {
                            data = property.GetValue(data, null);
                        }
                    }

                }
            }
            return data;
        }
    }
}
